在前面的章节里,我们明确了引用在Velocity模板里的角色。除了讨论如何直接通过变量和属性引用获取值之外,我们也介绍了方法引用,方法引用允许模板设计者通过定制的Java方法引用上下文对象操作和访问数据。方法引用提供在窗体里返回字符串的能力。虽然这些功能能够充分支持任何模板处理需要做的事情,但Velocity仍然为大家带来了模板语言,模板语言为许多通用任务提供了捷径。这就是Velocity指令表演的舞台。
指令(Directives)为控制模板处理流程、插入和操作上下文值、包含解析、停止解析文本、模板代码再使用(通过宏)提供了支持。Velocity指令是通过一个特定的前缀字符(#)后跟一个文本来定义的。Velocity目前支持总共10个指令,其中有一些是指令需要和其他指令结合才有意义。在这一章里,我们将详细介绍每一个指令。
#stop
第一个指令是#stop,主要用于程序调试。当模板引擎碰到这个指令时,引擎将终止执行并将控制返回到调用程序。特别的,合并上下文处理和模板的处理将停止在#stop指令处。这个处理将调用stopTemplate的合并方法(见Listing 8.1)。除(一些命名改变和许多附加的打印声明)以外,驱动代码(Listing 8.1)和在前面章节里的是同样的。
import java.io.StringWriter;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.exception.*;
public class Stop
{
public static void main( String[] args )
{
// Initialize template engine
try
{
Velocity.init();
}
catch( Exception x )
{
System.err.println( "Failed to initialize Velocity: " + x );
System.exit( 1 );
}
// Obtain a template
Template stopTemplate = null;
try
{
stopTemplate = Velocity.getTemplate( "Stop.vm" );
}
catch( ResourceNotFoundException rnfX )
{
System.err.println( "Template not found: " + rnfX );
System.exit( 1 );
}
catch( ParseErrorException peX )
{
System.err.println( "Failed to parse template: " + peX );
System.exit( 1 );
}
catch( Exception x )
{
System.err.println( "Failed to initialize template: " + x );
System.exit( 1 );
}
// Create context
VelocityContext context = new VelocityContext();
// Populate context
context.put( "before", "before the stop directive" );
context.put( "after", "after the stop directive" );
// Merge template and context
StringWriter writer = new StringWriter();
try
{
System.out.println( "***** Starting merge *****" );
stopTemplate.merge( context, writer );
System.out.println( "***** Returning from merge *****" );
}
catch( ResourceNotFoundException rnfX )
{
System.err.println( "Template not found on merge: " + rnfX );
System.exit( 1 );
}
catch( ParseErrorException peX )
{
System.err.println( "Failed to parse template on merge: " +
peX );
System.exit( 1 );
}
catch( MethodInvocationException miX )
{
System.err.println( "Application method exception: " + miX );
System.exit( 1 );
}
catch( Exception x )
{
System.err.println( "Failed to merge template: " + x );
System.exit( 1 );
}
// Render merged content
System.out.println( writer.toString() );
}
}
Listing 8.1 The driver program used for the #stop directive example.
Listing 8.1代码(使用的模板见Listing 8.2)的执行结果见Listing 8.3,它示范了Velocity的#stop指令的结果。从中你可以看到#stop指令导致调用合并在模板完全处理完之前返回。在#stop指令之前的模板内容被正常处理,其中包括适当的引用处理。在#stop指令之后的模板内容根本不进行处理。程序的非模板描述输出证明了#stop不能传入任何错误条件,但它可以让合并处理提前终止并正常返回,以便于程序调试。
## Start processing
=== Start of template ===
The portion of the template preceding the stop directive is
processed normally.
This is $before.
## Stop processing
#stop
The portion of the template following the stop directive is
not processed.
This is $after.
Listing 8.2 A template using the #stop directive.
***** Starting merge *****
***** Returning from merge *****
=== Start of template ===
The portion of the template preceding the stop directive is
processed normally.
This is before the stop directive.
Listing 8.3 Results from processing the template in Listing 8.2.
#include
#include指令和它的名字一样,其任务就是完成文件包含处理。它允许模板设计者包含模板外的文件。特别的,它允许包含来自一个或多个外来源的静态内容。这些内容将被放置在#include指令出现的地方,并进行输出。让我们来看一下Listing 8.4的示例,除了三条#include指令以外,模板仅仅由静态内容组成。假定#include所包含的“specials”内容因某些原因而改变,并假定“specials”仅仅是一个简单的文本文件,现在计划通过#include指令来重新得到这些文件的内容。如果这些文件的定义和Listing 8.5一样,模板处理后的输出结果应该是和Listing 8.6一样。
## Generic welcome
Welcome to Gem's Bar and Grill!
We are conveniently located at the corner of 5th and Ives in beautiful lower downtown.
Our three specials of the day are as follows:
## Present today's specials
#include( "special-1.txt" )
#include( "special-2.txt" )
#include( "special-3.txt" )
## Generic info
We are open from 4:00 P.M. to 2:00 A.M. daily.
Call 555-1234 for more information.
Listing 8.4 A template using #include directives.
special-1.txt: Quesadilla Pie — $7.95
special-2.txt: Gem's Famous Cheeseburger — $5.95
special-3.txt: Soup and Salad — $4.95
Listing 8.5 File definitions used for the #include examples.
Welcome to Gem's Bar and Grill!
We are conveniently located at the corner of 5th and Ives
in beautiful lower downtown.
Our three specials of the day are as follows:
Quesadilla Pie — $7.95
Gem's Famous Cheeseburger — $5.95
Soup and Salad — $4.95
We are open from 4:00 P.M. to 2:00 A.M. daily.
Call 555-1234 for more information.
Listing 8.6 Results from processing the template in Listing 8.4.
对于Velocity#include指令提供的服务没有更多好说的。在上面这个示例里,每一个#include指令通过相应的文件名字符串指定了一个单一文件。#include指令允许你在两个方面进行扩展。首先,你可以用一个Velocity引用来代替#include里的参数。下面的示例将演示这种情况,示例需要用的上下文封装代码见Listing 8.7,Listing 8.4模板代码需要重新书写,修改后的结果见Listing 8.8。最终处理结果没有任何改变。
// Populate context with file names used by #include
context.put( "special-1", "special-1.txt" );
context.put( "special-2", "special-2.txt" );
context.put( "special-3", "special-3.txt" );
Listing 8.7 Java code that populates the context with filenames used by the #include directive.
## Generic welcome
Welcome to Gem's Bar and Grill!
We are conveniently located at the corner of 5th and Ives
in beautiful lower downtown.
Our three specials of the day are as follows:
## Present today's specials
#include( $special-1 )
#include( $special-2 )
#include( $special-3 )
## Generic info
We are open from 4:00 P.M. to 2:00 A.M. daily.
Call 555-1234 for more information.
Listing 8.8 A template demonstrating the use of Velocity references with the #include directive.
第二种方式是#include指令允许你对指令的语法进行扩展。在第一个示例里是,需要包含的文件名是直接提供的。现在用字符串式的引用代替他们,这样,你就可以在一个#include指令包含多更多的文件。比如,在Listings 8.4和8.8示例里的#include三个指令就可以换成如下的方式:
#include( “special-1.txt” “special-2.txt” “special-3.txt” )
#include( $special-1 $special-2 $special-3 )
迄今为止,我们忽视了一个重要的部分,是关于如何使用Velocity的#include指令,既Velocity查找指定文件的方式。虽然文件的名称已经直接提供给了指令,但并没有提及如何获得相对于根目录的文件相对位置。Velocity是通过假定所有文件名都指定为相对于模板根目录来解决这个不明确的问题的。默认路径是应用程序开始运行的目录,但默认模板根路径位置可能被Velocity的运行时配置属性覆盖。具体情况将在Chapter 10(获得Velocity的控制Taking Control of Velocity)进行讨论。
#parse
#parse指令与#include指令相当相似,双方都用于从模板根路径位置的外部文件包含内容。它们的主要主要区别是:#include包含的内容将当成静态内容处理,#parse包含的内容将当成另外一个模板来处理。也就是说,#parse指令允许你嵌套模板。下面我们将用一个修改过的daily special示例(前面章节提及过)来说明#parse指令如何使用的。在这里,我们假定管理者想通过数据库来获取所有价格,而不是在包含文件里进行硬编码来获取价格。
第一步是修改上下文组合代码,以便能够获取当前价格和把当前价格插入到上下文中,具体代码见Listing 8.9,变量qPiePrice、 cBurgerPrice和sSaladPrice将从数据库是获取价格值,每一个值将通过和各自变量名称相匹配的字符串键入。
// Populate context
context.put( "qPiePrice", qPiePrice );
context.put( "cBurgerPrice", cBurgerPrice );
context.put( "sSaladPrice", sSaladPrice );
Listing 8.9 Our modified application code that inserts the daily special prices into the context.
下一步,我们将创建一些新文件来呈现daily specials。这些文件的定义是基于Listing 8.5的内容进行修改的。这些新文件被重新改名,价格被替换成Velocity变量引用。最后修改的结果见Listing 8.10。
special-1.vm: Quesadilla Pie — $qPiePrice
special-2.vm: Gem's Famous Cheeseburger — $cBurgerPrice
special-3.vm: Soup and Salad — $sSaladPrice
Listing 8.10 Files definitions used for #parse examples.
最后我们对Listing 8.4的模板进行更新,用#parse指令替换#include指令,用适当的文件名直接改文件名。更新后的模板见Listing 8.11,输出结果和Listing 8.6显示的是一样的。
## Generic welcome
Welcome to Gem's Bar and Grill!
We are conveniently located at the corner of 5th and Ives
in beautiful lower downtown.
Our three specials of the day are as follows:
## Present today's specials
#parse( "special-1.vm" )
#parse( "special-2.vm" )
#parse( "special-3.vm" )
## Generic info
We are open from 4:00 P.M. to 2:00 A.M. daily.
Call 555-1234 for more information.
Listing 8.11 A template using #parse directives.
在使用#include指令时,文件名或许可以通过Velocity引用提供。然而,和#include指令不同,#parse指令不支持多参数。因此,当我们按普通方式用下面的语句替换Listing 8.11的#parse行时
#parse( $special-1 )
#parse( $special-2 )
#parse( $special-3 )
使用单个#parse,比如
#parse( $special-1 $special-2 $special-3 )
将不能得到正确的输出结果。
在进行嵌套和递归时,#parse指令也和#include指令不同,neither of which makes sense in the context of an #include(??未译出)。嵌套(Nesting)引用了一个放置了#parse指令,并通过该#parse指令进行自包含的文件。递归(Recursion)引用了一种特定情况的嵌套,这种情况是#parse指令引用它自身的文件。默认情况下,Velocity限制#parse指令最多有10层的嵌套;然而,这个默认可能被Velocity的运行时配置属性重写(你将在Chapter 10里进行学习)。
下面展示了用#parse指令进行嵌套和递归的示例,它包含了下面的一个命名为Myself.vm的模板:
Before parse directive.
#parse( “Myself.vm” )
After parse directive.
假定最大的解析深度被设置成3,则模板的输出结果为:
Before parse directive.
Before parse directive.
Before parse directive.
After parse directive.
After parse directive.
After parse directive.
每一次这个文件被处理,其第一行都将被输出。然后,#parse指令将致使模板引擎启动处理这个文件的一个新拷贝。这样一直重复,直到最后一个#parse指令被处理或达到最大解析深度;在递归的情况下,位于其后的语句只有等到递归全部返回后才开始处理。最后,随着模板引擎从每一个#parse指令的返回,它将开始处理剩余的语句,直到输出这个简单模板的最后一行。
#set
和Velocity其他的指令不同,#set指令将影响上下文和模板的合并。你可以使用#set指令去更新一个已经存在的上下文实体,也可用于创建一个新的实体。这个实体,无论是更新还是新建,都是模板的可以立即(直接)使用的;然而,他们只能在模板和上下文合并完成之后才能使用。为了说明#set指令的基本原理,让我们进入一个简单的示例进行研究。假定我们已经有一个存在的用于存储字符串值“oldValue”(对应关键字为“existing”)的上下文实体。这时,我们想把模板里的实体值“oldValue”改成“newValue”,并增加一个新的具有字符串值为“newEntry”(对应关键字为“new”)的上下文。Listing 8.12的模板满足了这些需要,其输出结果见Listing 8.13。
## Before set directives
The initial value keyed by "existing" is $existing.
The initial value keyed by "new" is $!new.
#set( $existing = "newValue" )
#set( $new = "newEntry" )
## After set directives
The final value keyed by "existing" is $existing.
The final value keyed by "new" is $!new.
Listing 8.12 A template that uses the #set directive to update one entry and add another.
The initial value keyed by "existing" is oldValue.
The initial value keyed by "new" is .
The final value keyed by "existing" is newValue.
The final value keyed by "new" is newEntry.
Listing 8.13 Results from processing the template in Listing 8.12.
注意,模板表面上好象已经立即访问了这些更新后的和新增加的值,下面的应用程序并不是这种情况。事实上,所有上下文的模板初始化和改变都发生在合并处理期间。一旦这些指令以他们在模板里的顺序进行上下文合并和变更操作完成以后,模板里的引用就可以接收适当的更新数据。然而,直到这些处理过程完成,其下面的应用程序对这些改变一无所知。示例的Java代码见Listing 8.14,这些代码打印了“existing”和“new”上下文实体的值,在调用合并之前和在调用合并之后。当下面的应用程序开始运行时,这此代码将产生如下输出:
Before merge: oldValue/null
After merge: newValue/newEntry
System.out.println( "Before merge: " + context.get( "existing" ) + "/" + context.get( "new" ) );
setTemplate.merge( context, writer );
System.out.println( "After merge: " + context.get( "existing" ) + "/" + context.get( "new" ) );
Listing 8.14 Code querying the context before and after the merge of a template using #set directives.
在前面的示例里介绍#set指令时,为了使程序更加简单,我们仅仅为变量引用指定了一个简单的字符串值。虽然在第一个示例里介绍的,我们使用的是#set指令的普通语法(引用=值),但#set指令允许使用多种类型的引用类型和值类型,这就提高#set指令的能力和使用范围。从等号(=)左侧开始,为#set指令提供的引用可以是一个变更引用,也可以是一个属性引用。我们已经看到了#set指令使用变量引用的例子,下面让我们来一个使用属性引用的示例。
我们曾经在Chapter 7里讨论过属性引用依赖于反射机制(introspection)。在那些章节里,我们提供了一些使用反射的示例,在示例里,反射揭开了通过属性引用读取对象属性值的get方法原理。(In that chapter, we provided examples where introspection uncovered get methods that allowed object property values to be read through property references)。同样的,Velocity也允许通过窗体setValue(value)的set方法,使用属性引用修改对象属性值。下面让我们考虑一下Listing 8.15里的SetObj类,它提供了setValue() 和getValue()方法来访问它的值属性。假定这个类的实例被存储在上下文中(和关键字setObj对应),属性引用setObj.value可以和#set指令结合来更新这个值属性。模板代码如下:
#set( $setObj.value = “some value” )
public class SetObj
{
public void setValue( String value )
{
this.value = value;
}
public String getValue()
{
return (value);
}
private String value;
}
Listing 8.15 A simple class providing a set and get method for access to its single property.
现在把视线转移到#set指令等号“=”的右边,我们发现这个值可以是多种类型的值也可以含有运算符。值类型包括文本字符串、integer、引用、数组、值域操作和布尔值。注意,#set指令不允许把值指定为null,如果一个碰巧返回的值为null(比如一个方法引用),引用的值(在等号左边)将保持原样。#set指令支持的操作符包括简单的算术运算和布尔运算。我们已经看到了使用字符串值的示例,那么就让我们来看一个使用引用的示例。
#set指令支持三种类型的Velocity引用值:变量、属性和方法。在Listing 8.16里示范了这些引用的用法(假定上下文包含了两个SetObj类<见Listing 8.15>的实例)。模板一开始就创建了一个变量引用来保存值“A value”。然而用变量引用联合setObjA上下文对象来设置属性的值。之后,setObjA上下文对象被用于设置setObjB上下文对象的属性值,首先是属性引用,其次是方法引用。模板每一步都将输出值“A value”。
## Create a variable reference
#set( $varRef = "A value"" )
## Use a variable reference as the value
#set( $setObjA.value = $varRef )
$setObjA.value
## Use a property reference to set the value
#set( $setObjB.value = $setObjA.value )
$setObjB.value
## Use a method reference to set the value
#set( $setObjB.value = $setObjA.getValue() )
$setObjB.value
Listing 8.16 A template demonstrating the use of variable, property, and method references as values for the #set directive.
个别的,我们已经了解如何在#set指令里使用字符串和引用,但是你知不知道如何用一个单独的#set指令包含这两者?没问题,只需要把引用用双引号(“”)包裹并放入字符串中就行。Velocity将自动在内容里插入双引号里的引用值,一部分字符串将按照原样进行输出,同时引用将按照普通方式进行处理。从另一方面来讲,如果你需要包含一个外表看起来象引用的字符串,那么,你只需简单地用单引号(‘’)把引用包裹起来就行。
下一步,让我们考虑由范围操作符和数组列表创建的#set指令值。范围操作符的使用格式为[m..n],其中的m和n都是整数或存储有整数值的引用。和范围操作符一样,一个数组列表也是用方括号([])定义的。然而,数组的内容是用逗号来分隔的,它允许使用#set指令支持的所有类型的值,包括范围和任何其他的数组列表。范围和数组列表的实现模板见Listing 8.17,其对应的输出结果见Listing 8.18。
模板首先使用#set指令来创建一个整数范围和一个保存有整数值的变量引用范围。接着使用#set指令来创建一个字符串数组列表、一个整数、一个值域范围、一个布尔值和一个变量引用。在模板中的不同位置上,将查询这些新创建的引用的内容和属性信息。The referenced objects behave as instances of Java’s ArrayList class, and thus we adhere to the ArrayList interface when working with the new range and list references(涉及的对象行为作为Java ArrayList类的实例,并且当和新范围、新列表引用一起工作时,我们从此附加了ArrayList接口????)。
## Set range with literals
#set( $range1 = [0..9] )
#set( $m = 1 )
#set( $n = 10 )
## Set range with references
#set( $range2 = [$m..$n] )
## Use ArrayList interface to access first and last
First value of range2: $range2.get( 0 )
Last value of range2 : $range2.get( 9 )
## Build list with a string literal, numeric literal,
## boolean value, and variable reference
#set( $list = ["string", 2, [2..-5], false, $m] )
## Use ArrayList interface to access index of some value
Index of $m : $list.indexOf( 1 )
Index of false : $list.indexOf( false )
Index of 2 : $list.indexOf( 2 )
Index of string: $list.indexOf( "string" )
## Get the size and fourth element of the nested range
Size of nested range: $list.get( 2 ).size()
Fourth element of nested range: $list.get( 2 ).get( 3 )
Listing 8.17 A template that uses range operators and array lists with the #set directive.
First value of range2: 1
Last value of range2 : 10
Index of 1 : 4
Index of false : 3
Index of 2 : 1
Index of string: 0
Size of nested range: 8
Fourth element of nested range: -1
Listing 8.18 Results from processing the template in Listing 8.17.
下一步,#set指令支持的值类型列表是布尔类型。正如你预料的一样,布尔类型的值仅限于true和false。If the values were all that Velocity provided, the type wouldn’t buy much for the template designer since it is easy enough to store a Boolean value with a string or number.(如果Velocity提供了所有的值,虽然可以很容易地使用字符串和数字来充当布尔值,但模板设计者并不能使用太多的类型)。然而,Velocity也支持布尔操作符AND、OR和NOT。和许多编程语言一样,Velocity使用短路比较法进行布尔运算,意思是在表达式里,Velocity只考虑必须的运算来决定最终结果。比如,如果第一个元素是布尔AND,且第一个运算值是false时,那么在其之后的就不再进行运算。同样地,如果第一个元素是布尔OR,且第一个运算值是true时,那么在其之后的也同样不再进行运算。
在Velocity模板里,布尔操作符AND、OR和 NOT都是用符号&&、||和!表示的。分别地,布尔值通过true和false表示。在布尔表达式里允许使用圆括号用于分组,也可以在任何地主用引用来表示布尔值。我们展示了一个模板(Listing 8.19),用于示范不同的布尔操作,输出结果见Listing 8.20。
## Initialize some references with boolean values
#set( $bValA = true )
#set( $bValB = false )
#set( $bValC = false )
## Perform some boolean operations
#set( $taf = $bValA && $bValB )
true AND false = $taf
#set( $tanf = $bValA && !$bValB )
true AND NOT false = $tanf
#set( $tof = $bValA || $bValC )
true OR false = $tof
#set( $ntof = !$bValA || $bValC )
NOT true OR false = $ntof
#set( $paoa = ($bValA && $bValB) || (!$bValC && !$bValB) )
(true AND false) OR (NOT false AND NOT false) = $paoa
Listing 8.19 A template demonstrating the use of Boolean values and operations in #set directives.
true AND false = false
true AND NOT false = true
true OR false = true
NOT true OR false = false
(true AND false) OR (NOT false AND NOT false) = true
Listing 8.20 Results from processing the template in Listing 8.19.
最后,我们来到最后一个被Velocity #set指令支持的值类型:integer。integer支持的值域和Java的Integer类支持的值域相同,从–2147483648到2147483647之间的整数。在这个范围以外的值将导致integer溢出。而且只有标准10进制的整形符号允许用作integer值。10进制小数、科学计数法和其他进制将导致模板解析错误。引用表示的数字值可以和数字一起在算术表达式里交替使用。
除了允许模板设计者存储integer值外,#set指令还支持简单的算术运算。运算包括加、减、乘,整数除和取模,Velocity模板代码分别用符号+、-、*、/和%表示。圆括号可以用于表达式,并且优先级最高。Listing 8.21的模板示范了#set指令使用integer整形数字和整形操作符的例子,其中包括整数溢出的示例。输出结果见Listing 8.22。
## Set maximum and minimum Integer values
#set( $max = 2147483647 )
#set( $min = -2147483648 )
## Demonstrate overflow
#set( $maxo = $max + 1 )
$max + 1 = $maxo
#set( $mino = $min - 1 )
$min - 1 = $mino
## Addition
#set( $add = 1 + 1 )
1 + 1 = $add
## Subtraciton
#set( $sub = 1 - 2 )
1 - 2 = $sub
## Multiplication
#set( $mult = 2 * 2 )
2 * 2 = $mult
## Integer division
#set( $div = 10 / 6 )
10 / 6 = $div
## Modulus
#set( $mod = 10 % 6 )
10 % 6 = $mod
## Compound expression
#set( $comp = ((7 / 3) * 3) + (7 % 3) )
((7 / 3) * 3) + (7 % 3) = $comp
Listing 8.21 A template demonstrating the use of integer literals and operations in #set directives.
2147483647 + 1 = -2147483648
-2147483648 - 1 = 2147483647
1 + 1 = 2
1 - 2 = -1
2 * 2 = 4
10 / 6 = 1
10 % 6 = 4
((7 / 3) * 3) + (7 % 3) = 7
Listing 8.22 Results from processing the template in Listing 8.21.
#end
#end指令用于标识模板代码块的结束,它只有和Velocity的某几个指令结合才有意义,这些指令是#if、#foreach和#macro。当模板引擎碰到#end指令时,它就会认为该指令之前的#if、#foreach或#macro指令所标识的代码块到止结束。我们在讨论#if、#foreach或#macro这些指令的示例时,就会看到,他们必须依赖#end指令来结束代码块。
#if
正如其名字暗示的一样,Velocity的#if指令为模板代码的条件处理提供了支持。最简单的说法就是,#if放置在一个有条件的模板代码块之前,并用#end指令结束这个代码块。如果条件运算的结果为true,这个代码块就会被处理,其处理结果将会插入到输出中。与此相反,如果条件运算的结果为false,这个代码块就会被忽略,并不会产生输出。如果布尔表达式或引用的结果为非null值或为布尔true时,就认为条件运算的结果为true。否则,会被认为是false。Listing 8.23的示例示范了一个使用#if指令的模板,其输出结果见Listing 8.24。
## Trivial example with boolean literal
#if ( true )
The condition literal is true!
#end
## Examples using boolean conditions
#set( $condition = true )
#if( $condition )
The condition reference is true!
#end
#set( $condition = false )
#if( $condition )
This test is never output! The condition reference is false.
#end
## Example using non-boolean condition
#set( $refValue = "A string" )
#if( $refValue )
A string is true!
#end
## Example using non-existent (null) condition
#if ( $nullValue )
This text is never output! Reference is null.
#end
Listing 8.23 A template demonstrating simple uses of the #if directive.
The condition literal is true!
The condition reference is true!
A string is true!
Listing 8.24 Results from processing the template in Listing 8.23.
在前面的示例里,我们限制条件表达式仅由简单的引用和布尔值组成。然而,#if指令也支持布尔表达式。布尔表达式由引用、布尔值、布尔操作符和关系运算符等组成。在此这前,我们讨论了布尔操作符AND (&&)、OR (||)和NOT (!)。关系运算符包括小于、大于、小于等于、大于等于、不等于和等于,在Velocity模板中分别用<、>、<=、>=、!=和==表示。这些关系运算符只允许在整数和具有(符合Velocity整形值标准的)整形值的引用之间运算。一般的,你可以在表达式中使用圆括号来明确优先运算权。Listing 8.25中的模板示范了如何在#if指令里使用布尔运算、关系运算和混合表达式的例子。
## Example using boolean operators
#if ( ($bValA && !$bValB) || $bValB )
Boolean expression evaluates to true.
#end
## Examples using relational operators
#if ( $iVal < 1 ) less-than #end
#if ( $iVal > 1 ) greater-than #end
#if ( $iVal <= 1 ) less-than-equal #end
#if ( $iVal >= 1 ) greater-than-equal #end
#if ( $iVal != 1 ) not-equal #end
#if ( $iVal == 1 ) equal #end
## Example mixing boolean and relational operators
#if ( ($iVal >= 1) && (!($iVal == 1) || $bValB) )
The expression evaluates to true.
#end
Listing 8.25 A template demonstrating Boolean and relational expressions used for #if directive conditions.
让我们看一下Listing 8.26的例子。第一个#if指令包含四个独立的条件运算。当混合条件全部有效时,理解并不困难。同样,由于它自身的复杂性,在代码维护期间,更容易发生错误。
第二个#if指令采用的是#if指令嵌套块,由四个简单的条件组成,功能和第一个完全一样,但它更易读,也更易维护。给$x、$y、$allow和$sleeping设置任何一个值,其运算结果都是一样。
虽然关系操作符和布尔操作符为条件表达式提供了有力的工具,但是这样的表达式却损害了程序的易读性和可维护性。在许多情况下,使用#if指令嵌套可以让一些较为复杂的条件表达式变得更明朗、更易读。让我们看一下Listing 8.26的示例。
## Single complex condition
#if ( ($x < 5) && ($y < 3 || $y >= 9) && $allow && !$sleeping )
Take action
#end
## Complex condition rewritten as four simple nested conditions
#if ( $x < 5 )
#if ( $y < 3 || $y >= 9 )
#if ( $allow )
#if ( !$sleeping )
Take action
#end
#end
#end
#end
Listing 8.26 A template demonstrating nested #if directives.
#else
#else指令只有和Velocity的#if指令一起配合使用时才有效。它充当的是一个双重角色,终止一个模板代码块并初始化另一个模板代码块。在第一个角色里,当#if指令的条件运算结果为true时,它的表现和#if指令的#end指令相似,用作关闭模板代码块的定界符。在第二个角色里,当#if指令的条件运算结果为false时,它充当一个模板代码块的开始定界符。简短点说,就是 #else指令为#if指令指定一个当#if的条件运算为false时需要执行的模板代码块。这个新指定的代码块终止于#end指令。在Listing 8.27的示例里演示了#else指令的用法,其对应的输出见Listing 8.28。
## An if/else with a true condition
#set( $condition = true )
#if ( $condition )
With a condition of $condition, we get the 'if' block.
#else
With a condition of $condition, we get the 'else' block.
#end
## Try one with a false condition
#set( $condition = false )
#if ( $condition )
With a condition of $condition, we get the 'if' block.
#else
With a condition of $condition, we get the 'else' block.
#end
Listing 8.27 A template demonstrating the use of #else directives.
With a condition of true, we get the 'if' block.
With a condition of false, we get the 'else' block.
Listing 8.28 Results from processing the template in Listing 8.27.
#elseif
不要感到惊讶,Velocity的#elseif指令只能与#else和#if指令配合使用才有效。#else指令只能为#if指令的模板代码块提供无条件的二选一能力,而#elseif指令则可以提供有条件的二选一能力。和#else指令相似,#elseif指令也充当了两个角色,也就是,充当前面代码块的关闭定界符,同时作为交替代码块的开始定界符。这个交替代码块只有在#elseif指令后面的条件运算结果为true的时候才进行处理。最简单的情况下,#end指令用于终止这个交替代码块。Listing 8.29示范了一个使用#elseif指令的模板。这个模板从本质上讲和Listing 8.27的模板是相同的,但#else指令被替换成#elseif指令,并为#elseif指令指定了条件运算。这个模板的运算结果与Listing 8.27相同,详见Listing 8.30。
## An if/elseif with a true condition
#set( $condition = true )
#if ( $condition )
With a condition of $condition, we get the 'if' block.
#elseif ( !$condition )
With a condition of $condition, we get the 'elseif' block.
#end
## Try one with a false condition
#set( $condition = false )
#if ( $condition )
With a condition of $condition, we get the 'if' block.
#elseif (!$condition )
With a condition of $condition, we get the 'elseif' block.
#end
Listing 8.29 A template demonstrating the use of #elseif directives.
With a condition of true, we get the 'if' block.
With a condition of false, we get the 'elseif' block.
Listing 8.30 Results from processing the template in Listing 8.29.
在Listing 8.29里的模板清楚地示范了如何使用Velocity的#elseif指令,但这个示例并不能展示#elseif指令相对于#else指令的到底有些什么优势?第一个优势是,#elseif指令可以拥有自己的条件判断,并不依赖与之对应的#if指令;与此相反,#else指令不能拥有自己的条件判断,其条件判断必须依赖于与之对应的#if指令。第二个优势是,一个#if指令可以拥有多个#elseif指令,在这种情况下,#elseif指令的条件判断的结果将决定执行哪个交替模板代码块,除此而外的所有其他代码块都将被忽略。
当在一个单独的#if结构里出现多个#elseif指令时,每一个#elseif用于终止前一个#elseif的代码块,或作为第一个#elseif时,用于终止#if的代码块。如果没有无条件二选一的情况,则用#end指令终止最后一个#elseif代码块。如果必须要使用无条件二选一(即所有的条件均为false时,就执行无条件代码块),那么就需要用#else指令来终止最后一个#elseif代码块,最后用一个#end指令来终止该#else代码块。Listing 8.31里的模板演示了这种情况。
#set( $isDawn = false )
#set( $isNoon = false )
#set( $isDusk = false )
## Multiple #elseif directives
#if ( $isDawn )
The sun is rising.
#elseif ( $isNoon )
The sun is overhead.
#elseif ( $isDusk )
The sun is setting.
#end
## Multiple #elseif directives with closing #else directive
#if ( $isDawn )
The sun is rising.
#elseif ( $isNoon )
The sun is overhead.
#elseif ( $isDusk )
The sun is setting.
#else
What time is it?
#end
Listing 8.31 A template demonstrating the use of multiple #elseif directives, without and with a closing #else directive.
#if指令允许进行嵌套。任何一个#if结构都是由#if开始,用#end指令结束的,当然,在其中还包括#elseif、#else和相应的模板代码块以及可能嵌套的#if结构。Listing 8.32里的模板演示了一个#if结构的嵌套例子。最外层的#if结构首先检查$red是否为true,如果不是,它就转到与其搭配的#elseif,继续检查$blue是否为true,如果仍旧不是,则执行与其对应的#else代码块。外部的判断结果决定了内部的结构能否被执行。比如,如果最外层的#if结构检查$red的结果为true,模板引擎将处理嵌套的第一个#if结构,所有其他的内置结构将被忽略。
另外需要注意的是,Velocity不支持多层嵌套。
#if ( $red ) ## outer
#if ( $blue ) ## inner
Purple
#elseif ( $yellow ) ## inner
Orange
#else ## inner
Red and What?
#end ## inner
#elseif ( $blue ) ##outer
#if ( $yellow ) ## inner
Green
#else ## inner
Blue and What?
#end ## inner
#else ## outer
#if ( $yellow ) ## inner
We only mix yellow with red or blue
#end ## inner
#end ## outer
Listing 8.32 A template demonstrating the use of nested #if, #elseif, and #else directives.
#foreach
Velocity的#foreach指令为模板设计者提供了多次重复处理相同模板代码块的能力。更精确一点,它提供了基于一个列表进行反复处理的能力,当每次从列表里读取一个列表成员时就对这个组合代码块进行一次处理。#foreach指令放置在代码块的开始处,and as with the #if related directives,#end放在代码块的末尾。#foreach指令的语法形式为#foreach (REF in LIST),LIST对应成员的列表,REF对应Velocity变量引用,它引用了当前的成员列表。模板引擎执行#foreach指令联合代码块的次数和LIST的大小相等。每执行一次重复时,REF成员的值依次从列表的第一成员到最后一个成员进行更新。
在指定#foreach指令的LIST值时,有一些弹性。Listing 8.33的模板示范了两个比较简单的情况。第一种,通过操作数范围来提供LIST,#foreach指令重复是基于列表1, 2, 3, 4, 5的。第二种,通过数组列表来提供LIST,#foreach指令重复是基于数组列表“one”, “two”, “three”, “four”, “five”的。其输出结果见Listing 8.34。
Iterating over a range...
#foreach ( $item in [1..5] )
On this iteration, "$item refers to the value $item.
#end
Iterating over an array list...
#foreach ( $item in ["one", "two", "three", "four", "five"] )
On this iteration, "$item refers to the value $item.
#end
Listing 8.33 A template demonstrating the use of #foreach directives.
Iterating over a range...
On this iteration, $item refers to the value 1.
On this iteration, $item refers to the value 2.
On this iteration, $item refers to the value 3.
On this iteration, $item refers to the value 4.
On this iteration, $item refers to the value 5.
Iterating over an array list...
On this iteration, $item refers to the value one.
On this iteration, $item refers to the value two.
On this iteration, $item refers to the value three.
On this iteration, $item refers to the value four.
On this iteration, $item refers to the value five.
Listing 8.34 Results from processing the template in Listing 8.33.
除了使用操作数范围和数组列表为#foreach指令指定固定列表之外,Velocity还允许你通过模板上下文来指定列表。为了使用上下文对象来为#foreach指令指定列表,这个上下文对象必须是一个Java对象数组(Object[])或是一个实现了许多Java接口的对象。这些允许的接口包括Collection、Map、Iterator和Enumeration。Listing 8.35的模板包含了以上类型的示例,上下文封装见Listing 8.36,输出结果见Listing 8.37。
## Object Array
Iterating over Object array...
#foreach ( $elem in $objectArray ) The element is $elem on this iteration.
#end
## Map Interface
Iterating over Hashtable values...
#foreach ( $value in $hashtable ) The value is $value on this iteration.
#end
## Collection Interface
Iterating over Hashtable keys...
#foreach ( $key in $hashtable.keySet() ) The key is $key on this iteration.
#end
## Enumeration Interface
Iterating over Vector elements...
#foreach ( $elem in $vector.elements() ) The element is $elem on this iteration.
#end
## Iterator Interface
Iterating over LinkedList elements...
#foreach ( $elem in $linkedList.listIterator() ) The element is $elem on this iteration.
#end
Listing 8.35 A template demonstrating the use of the #foreach directive with lists taken from the context.
// Create and initialize context objects
Object[] objAr = new Object [3];
objAr[0] = "0";
objAr[1] = new Integer( 1 );
objAr[2] = "2";
Hashtable hash = new Hashtable();
hash.put( "A", new Integer( 65 ) );
hash.put( "B", new Integer( 66 ) );
hash.put( "C", new Integer( 67 ) );
Vector vec = new Vector();
vec.add( "Hickory" );
vec.add( "Dickory" );
vec.add( "Dock" );
LinkedList list = new LinkedList();
list.add( "Red" );
list.add( "Green" );
list.add( "Blue" );
// Populate context
context.put( "objectArray", objAr );
context.put( "hashtable", hash );
context.put( "vector", vec );
context.put( "linkedList", list );
Listing 8.36 Context population code used with the template in Listing 8.35 to generate the output shown in Listing 8.37.
Iterating over Object array...
The element is 0 on this iteration.
The element is 1 on this iteration.
The element is 2 on this iteration.
Iterating over Hashtable values...
The value is 65 on this iteration.
The value is 67 on this iteration.
The value is 66 on this iteration.
Iterating over Hashtable keys...
The key is A on this iteration.
The key is C on this iteration.
The key is B on this iteration.
Iterating over Vector elements...
The element is Hickory on this iteration.
The element is Dickory on this iteration.
The element is Dock on this iteration.
Iterating over LinkedList elements...
The element is Red on this iteration.
The element is Green on this iteration.
The element is Blue on this iteration.
Listing 8.37 Output generated by the template in Listing 8.35 when using the context defined in Listing 8.36.
正如你在Listing 8.37里所看到哈希表一样,列表成员的次序和它们插入到列表的次序不必相同。#foreach指令将按列表的次序,依次从第一成员移动到最后一个成员;然而,它本身的次序将由容器来决定,当在Java代码里直接完成相同反复的类型时,就是一样的(just as it is
)。
在之前的示例里,我们并没有尝试明确的区别反复(iteration),我们只让当前列表成员引用它本身(speak for itself)?然而,了解和掌握反复(iteration)比较有用,比如当进行标签输出或在已确定反复的情况执行特定行为时。虽然你可以使用#set指令来初始化和增加一个循环记数器,但这样的处理方式相当乏味。Velocity将忙于(address)处理这个问题(issue)(通过提供一个特定的用作#foreach指令循环计数器的变量引用)。默认情况下,这个变量被命名为$velocityCount,但是这个名字可能被Velocity的运行时配置重载。Listing 8.38的模板提供了一个使用Velocity的内置循环计数器的示例。输出结果见Listing 8.39。
## Track the iteration with Velocity's loop counter
#foreach ( $outer in [-1..1] )
Iteration $velocityCount of outer loop: $outer
#foreach ( $inner in ["one", "two"] )
Iteration $velocityCount of inner loop: $inner
#end
#end
Listing 8.38 A template demonstrating the use of Velocity’s loop counter reference.
Iteration 1 of outer loop: -1
Iteration 1 of inner loop: one
Iteration 2 of inner loop: two
Iteration 2 of outer loop: 0
Iteration 1 of inner loop: one
Iteration 2 of inner loop: two
Iteration 3 of outer loop: 1
Iteration 1 of inner loop: one
Iteration 2 of inner loop: two
Listing 8.39 Results from processing the template in Listing 8.38.
除了进行简单地示范Velocity的循环计数器之外,Listings 8.38 和8.39突出了两个#foreach指令的其他重要方面。第一个方面是Velocity支持#foreach指令嵌套;它也支持嵌套其他指令。第二个方面是循环计数器的作用域范围是当前#foreach指令。事实上,当模板引擎从外面的#foreach指令移动到一个内部的#foreach指令时,外围#foreach指令的循环计数器被保存,同时,一个为内部#foreach指令计数新的计数器被初始化。当控制返回到外部指令时,外部指令原来保存的计数器值被还原。
#macro
Velocity的#macro指令提供了一个模板代码复用的机制。它和#parse指令的功能比较相似,但#macro提供了更多的弹性和控制能力。为了导入和处理所有的被任意文件包含的模板代码,作为代替,#macro指令提供了一个语法用一指定和命名一个模板代码块,包括对参数输入的支持。代码块可以在宏库里指定,也可在有秩序的模板文件里(inline in a regular template file)指定。一旦被定义,这个代码块就可以通过普通的Velocity指令语法进行访问。既然在Chapter 9里会介绍Velocity的宏(“Introducing Velocimacros”),并详细介绍#macro指令,那么在这里,我们只作一个快速尝试。Listing 8.40展示一个简单的示例,它内置(inline)了#macro指令。在模板文件里定义了一个名称为sayHi的宏,并且用#sayHi()进行调用。输出结果为Hello world。
## Define inline macro for this template
#macro( sayHi )
Hello world
#end
## Invoke the macro using normal directive syntax
#sayHi()
Listing 8.40 A template demonstrating the use of the #macro directive.
转义/忽略指令(Escaping Directives)
有些时候,我们需要阻止模板引擎停止处理一个指令。很明显的例子就是我们在讨论Velocity模板语法时有关动态内容的各种情况。在那种情况下,需要经常把指令当作字符串对待,虽然外表看起来像Velocity的引用,但我们只想把它当作文本来处理。作为Velocity的引用,这是通过使用反斜线(")换码符来完成的。事实上,转义处理指令从本质上讲同我们在Chapter 7讨论过的引用一样,包括忽略换码符(escape character)、和(放置引用/指令的地方与不需要定义)这两种情况下的行为差异。绑定召回(Recall that binding)是从左到右的,每一对换码符被压缩成单个反斜线,虽然在指令(或宏)没有定义的情况下,这个行为稍微有点古怪。Listings 8.41和8.42提供了一个模板和相应的输出。
## Valid directive
Output for valid directive with varying numbers of escapes.
#include( "info.txt" )
"#include( "info.txt" )
""#include( "info.txt" )
"""#include( "info.txt" )
""""#include( "info.txt" )
"""""#include( "info.txt" )
## Undefined directive/macro
Output for undefined directive/macro with varying numbers of escapes.
#xinclude( "info.txt" )
"#xinclude( "info.txt" )
""#xinclude( "info.txt" )
"""#xinclude( "info.txt" )
""""#xinclude( "info.txt" )
"""""#xinclude( "info.txt" )
Listing 8.41 A template demonstrating the use of directive escapes.
Output for valid directive with varying numbers of escapes.
Included information.
#include( "info.txt" )
"Included information.
"#include( "info.txt" )
""Included information.
""#include( "info.txt" )
Output for undefined directive/macro with varying numbers of escapes.
#xinclude( "info.txt" )
"#xinclude( "info.txt" )
""#xinclude( "info.txt" )
"""#xinclude( "info.txt" )
""#xinclude( "info.txt" )
"""""#xinclude( "info.txt" )
Listing 8.42 Results from processing the template in Listing 8.41.
本章小节和下章介绍
在本章里,我们详细讨论了Velocity指令,它极大地赋予模板设计者对模板进行处理的能力。我们提供了众多的示例来示范如何使用这些指令来控制处理流程、插入内容、操作上下文和对模板代码进行重复使用。我们只是暂时介绍了#macro指令,我们将在下一章对这个指令进行详细介绍。